﻿using System;
using System.Collections.Generic;
using System.Text;

using Custom.Crm.Sdk;

using Microsoft.Crm.Sdk;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using Microsoft.Crm.Sdk.Metadata;

namespace Custom.Crm
{
    public class Audit : CustomTargetImage, IPlugin
    {
        /// <summary>Name of the entity where the audit data will be stored.</summary>
        private string m_AuditEntity = "custom_audit";

        /// <summary>Name of the entity where the detail of the audit will be stored.</summary>
        private string m_AuditDetailEntity = "custom_audit_detail";

        /// <summary>The max length of a logged value.  Greater than this - save as a note.</summary>
        private int m_MaxLength = 100;

        /// <summary>The message to use if the value is too big and saved as a note.</summary>
        private string m_MsgTooBig = "(Value saved as a Note)";

        /// <summary>Name of the lookup field on the audit table that links to the calling entity.</summary>
        private string m_LookupKey = null;

        /// <summary>
        /// Constructor for the class - recieves two variables when the workflow engine.
        /// </summary>
        /// <param name="unSecure">Unsecure data entered while registering the plugin.
        /// Example of unSecure configuration:  <Configuration><AuditEntity>new_contact_audit</AuditEntity><KeyField>new_contactid</KeyField></Configuration>
        /// </param>
        /// <param name="Secure">secure data entered while registering the plugin</param>
        public Audit(string unSecure, string Secure)
        {
            if (unSecure != null && unSecure.Length > 0)
            {
                // Get the Configurations from the unSecure properties.
                Configuration m_config = new Configuration(unSecure);

                // The config item that holds the audit entity name.
                string AuditEntity = m_config["AuditEntity"];
                if (AuditEntity != null) m_AuditEntity = AuditEntity;

                // The config item that holds the audit entity name.
                string AuditDetailEntity = m_config["AuditDetailEntity"];
                if (AuditDetailEntity != null) m_AuditDetailEntity = AuditDetailEntity;

                // The config item that holds the max length of the audit detail value fields.
                string MaxLength = m_config["MaxLength"];
                if (MaxLength != null) m_MaxLength = int.Parse(MaxLength);

                // The config item that holds the too big big value message.
                string MsgTooBig = m_config["MessageTooBig"];
                if (MsgTooBig != null) m_MsgTooBig = MsgTooBig;

                // The config item that indicates whether a lookup property needs to be set.
                m_LookupKey = m_config["KeyField"];
            }
        }

        public void Execute(IPluginExecutionContext context)
        {
            // Get a reference to the CRM Web Service & metadata service
            ICrmService crmService = (ICrmService)context.CreateCrmService(true);
            IMetadataService metaData = context.CreateMetadataService(true);

            // Get the metadata about the current entity.
            RetrieveEntityRequest req = new RetrieveEntityRequest();
            req.EntityItems = Microsoft.Crm.Sdk.Metadata.EntityItems.IncludeAttributes;
            req.LogicalName = context.PrimaryEntityName;
            RetrieveEntityResponse res = (RetrieveEntityResponse)metaData.Execute(req);

            // Create the record & set it to the audit entity.
            DynamicEntity auditRecord = new DynamicEntity();
            auditRecord.Name = m_AuditEntity;

            // Set the Entity name, Message Type, Primary attribute value.
            auditRecord.Properties["custom_entity"] = context.PrimaryEntityName;
            auditRecord.Properties["custom_entity_label"] = res.EntityMetadata.DisplayName.UserLocLabel.Label;
            auditRecord.Properties["custom_primary_attribute"] = this.GetValue(context, res.EntityMetadata.PrimaryField);
            auditRecord.Properties["custom_message"] = context.MessageName;

            // Get the current object's id and save it for audit.
            Guid entityId = this.GetKeyValue(context, res.EntityMetadata.PrimaryKey);
            if (entityId != Guid.Empty)
                auditRecord.Properties["custom_entityid"] = entityId.ToString("D");

            // If the Audit entity has a relationship to the Primary entity, then populate the lookup key.
            if (m_LookupKey != null && entityId != Guid.Empty)
                auditRecord.Properties[m_LookupKey] = new Lookup(context.PrimaryEntityName, entityId);

            // Create the audit record.
            Guid auditId = crmService.Create(auditRecord);

            // Process the modified records
            if (context.PostEntityImages.Contains("Target"))
            {
                // Get a reference to the images.
                DynamicEntity pre = null;
                DynamicEntity post = (DynamicEntity)context.PostEntityImages["Target"];

                // If the message is "Create" then dummy up a pre-image.
                if (context.MessageName == "Create")
                    pre = new DynamicEntity();
                else
                    pre = (DynamicEntity)context.PreEntityImages["Target"];

                // Iterate through the entity's attributes to determine change.
                foreach (AttributeMetadata metaAttribute in res.EntityMetadata.Attributes)
                {
                    // Ensure the attribute is a parent level attribute.
                    if (metaAttribute.AttributeOf == null)
                    {
                        // Get the property objects
                        object preProp = this.GetObjectFromProperty(pre.Properties, metaAttribute.LogicalName);
                        object postProp = this.GetObjectFromProperty(post.Properties, metaAttribute.LogicalName);

                        // Ensure we have at least a pre/post property.
                        bool bProcess = (preProp != null);
                        if (!bProcess) bProcess = (postProp != null);

                        if (bProcess)
                        {
                            // Get the value of the properties
                            string preVal = CustomDynamicEntity.GetAttributeValue(preProp, true);
                            string postVal = CustomDynamicEntity.GetAttributeValue(postProp, true);

                            // Has the property changed?
                            if (preVal != postVal)
                            {
                                // Create an object for saving the changed detail.
                                DynamicEntity auditDetail = new DynamicEntity();
                                auditDetail.Name = m_AuditDetailEntity;
                                auditDetail.Properties["custom_auditid"] = new Lookup(m_AuditEntity, auditId);

                                // Set the detail for the attribute detail change.
                                auditDetail.Properties["custom_display_name"] = metaAttribute.DisplayName.UserLocLabel.Label;
                                auditDetail.Properties["custom_schema_name"] = metaAttribute.LogicalName;

                                // Determine whether a value is too big for the value field.
                                bool bPreValueTooBig = false;
                                bool bPostValueTooBig = false;

                                // Evaluate too big for Pre Value.
                                if (preVal != null)
                                {
                                    if (preVal.Length > m_MaxLength)
                                    {
                                        bPreValueTooBig = true;
                                        auditDetail.Properties["custom_original_value"] = m_MsgTooBig;
                                    }
                                    else
                                        auditDetail.Properties["custom_original_value"] = preVal;
                                }

                                // Evaluate too big for Post Value.
                                if (postVal != null)
                                {
                                    if (postVal.Length > m_MaxLength)
                                    {
                                        bPostValueTooBig = true;
                                        auditDetail.Properties["custom_new_value"] = m_MsgTooBig;
                                    }
                                    else
                                        auditDetail.Properties["custom_new_value"] = postVal;
                                }

                                // Create the audit detail record.
                                Guid auditDetailId = crmService.Create(auditDetail);

                                // If the Post Value is too big, save it as a note.
                                if (bPostValueTooBig)
                                {
                                    const string C_POST_NOTE_TOOBIG_SUBJECT = "Modified Value";
                                    this.CreateNote(crmService, m_AuditDetailEntity, auditDetailId, C_POST_NOTE_TOOBIG_SUBJECT, postVal);
                                }

                                // If the Pre Value is too big, save it as a note.
                                if (bPreValueTooBig)
                                {
                                    const string C_PRE_NOTE_TOOBIG_SUBJECT = "Original Value";
                                    this.CreateNote(crmService, m_AuditDetailEntity, auditDetailId, C_PRE_NOTE_TOOBIG_SUBJECT, preVal);
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets an Object from a Dynamic Entity's Property collection
        /// </summary>
        /// <param name="col"></param>
        /// <param name="propertyName"></param>
        /// <returns></returns>
        private object GetObjectFromProperty(PropertyCollection col, string propertyName)
        {
            if (col.Contains(propertyName))
                return col[propertyName];
            else
                return null;
        }

        private void CreateNote(ICrmService crmService, string entityName, Guid entityId, string subject, string body)
        {
            // Create the Note object and set the parent object.
            annotation note = new annotation();
            note.objectid = new Lookup(entityName, entityId);
            note.objecttypecode = new EntityNameReference(entityName);

            // Set the note text and subject
            note.subject = subject;
            note.notetext = body;

            // Create the note 
            crmService.Create(note);
        }
    }
}
